// (c) Copyright Fernando Cerqueira.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;

// ReSharper disable CheckNamespace
namespace DevPhone.Controls
// ReSharper restore CheckNamespace
{
    public class NullableDateTimeTypeConverter : TypeConverter
    {

        /// <summary>
        /// Backing field for the TimeFormats being used.
        /// </summary>
        private static readonly string[] TimeFormats = new[]
                                                           {
                                                               "h:mm tt",
                                                               "h:mm:ss tt",
                                                               "HH:mm",
                                                               "HH:mm:ss",
                                                               "H:mm",
                                                               "H:mm:ss"
                                                           };


        /// <summary>
        /// Backing field for the DateFormats being used.
        /// </summary>
        private static readonly string[] DateFormats = new[]
                                                           {
                                                               "M/d/yyyy"
                                                           };

        /// <summary>
        /// Determines whether this instance can convert from 
        /// the specified type descriptor context.
        /// </summary>
        /// <param name="typeDescriptorContext">The type descriptor context.</param>
        /// <param name="sourceType">Type of the source.</param>
        /// <returns>
        /// 	<c>True</c> if this instance can convert from the specified type 
        /// descriptor context; otherwise, <c>false</c>.
        /// </returns>
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
        public override bool CanConvertFrom(ITypeDescriptorContext typeDescriptorContext, Type sourceType)
        {
            return Type.GetTypeCode(sourceType) == TypeCode.String;
        }

        /// <summary>
        /// Determines whether this instance can convert to the specified 
        /// type descriptor context.
        /// </summary>
        /// <param name="typeDescriptorContext">The type descriptor context.</param>
        /// <param name="destinationType">Type of the destination.</param>
        /// <returns>
        /// 	<c>True</c> if this instance can convert to the specified type 
        /// descriptor context; otherwise, <c>false</c>.
        /// </returns>
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
        public override bool CanConvertTo(ITypeDescriptorContext typeDescriptorContext, Type destinationType)
        {
            return Type.GetTypeCode(destinationType) == TypeCode.String || CanConvertTo(destinationType);
        }

        /// <summary>
        /// Converts instances of other data types into instances of DateTime that
        /// represent a time.
        /// </summary>
        /// <param name="typeDescriptorContext">
        /// The type descriptor context.
        /// </param>
        /// <param name="cultureInfo">The culture used to convert. This culture
        /// is not used during conversion, but a specific set of formats is used.</param>
        /// <param name="source">
        /// The string being converted to the DateTime.
        /// </param>
        /// <returns>
        /// A DateTime that is the value of the conversion.
        /// </returns>
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Compat with WPF.")]
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Compat with WPF.")]
        public override object ConvertFrom(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object source)
        {
            if (source == null)
            {
                return null;
            }

            var text = source as string;

            if (text == null)
            {
                throw new InvalidCastException("Source is not string");
            }

            if (string.IsNullOrEmpty(text))
            {
                return null;
            }

            var result = new DateTime();
            
            // test using date only
            if (DateFormats.Any(dateFormat => DateTime.TryParseExact(text, (string) dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)))
            {
                return result;
            }

            // test using combinations of date and time
            if (DateFormats.Any(dateFormat => TimeFormats.Any(timeFormat => DateTime.TryParseExact(text, String.Format(CultureInfo.InvariantCulture, "{0} {1}", dateFormat, timeFormat), CultureInfo.InvariantCulture, DateTimeStyles.None, out result))))
            {
                return result;
            }

            throw new FormatException("date format invalid.");
        }

        /// <summary>
        /// Converts a DateTime into a string.
        /// </summary>
        /// <param name="typeDescriptorContext">
        /// The type descriptor context.
        /// </param>
        /// <param name="cultureInfo">The culture used to convert.</param>
        /// <param name="value">
        /// The value that is being converted to a specified type.
        /// </param>
        /// <param name="destinationType">
        /// The type to convert the value to.
        /// </param>
        /// <returns>
        /// The value of the conversion to the specified type.
        /// </returns>
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Compat with WPF.")]
        public override object ConvertTo(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                if (value == null)
                {
                    return String.Empty;
                }
                if (value is DateTime)
                {
                    var time = (DateTime)value;
                    return time.ToString("M/d/yyyy", new CultureInfo("en-US"));
                }
            }
            return base.ConvertTo(typeDescriptorContext, cultureInfo, value, destinationType);
        }
    }
}